﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;
using System.Diagnostics;

namespace Pipra.Math.Geometry
{
    /// <summary>
    /// A 2D axis aligned minimum bounding rectangle, sometimes known as an envelope.
    /// </summary>
    public struct Mbr :
        IMbr<double>,
        IEquatable<Mbr>,
        IIntersectable<Point2>,
        IHasDistance<double,Point2>,
        IIntersectable<Mbr>,
        IContainable<Mbr>,
        ITouches<Mbr>,
        IOverlappable<Mbr>,
        ICanBeDisjoint<Mbr>,
        ICanContain<Mbr>,
        ICanCross<Mbr>,
        IHasDistance<double, Mbr>
    {
        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">A MBR.</param>
        /// <param name="b">A MBR.</param>
        /// <returns>True if both MBRs have the same X and Y ranges.</returns>
        public static bool operator ==(Mbr a, Mbr b)
        {
            return a.Equals(b);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">A MBR.</param>
        /// <param name="b">A MBR.</param>
        /// <returns>True if both MBRs do not have the same X and Y ranges.</returns>
        public static bool operator !=(Mbr a, Mbr b)
        {
            return !a.Equals(b);
        }

        /// <summary>
        /// An invalid point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Mbr Invalid = new Mbr(Double.NaN, Double.NaN);

        /// <summary>
        /// The x-axis range.
        /// </summary>
        public readonly Range X;
        /// <summary>
        /// The y-axis range.
        /// </summary>
        public readonly Range Y;
        /// <summary>
        /// Constructs an MBR encapsulating the coordinate <paramref name="x"/>,<paramref name="y"/>.
        /// </summary>
        /// <param name="x">The x-coordinate.</param>
        /// <param name="y">The y-coordinate.</param>
        public Mbr(double x, double y)
        {
            X = new Range(x);
            Y = new Range(y);
        }
        /// <summary>
        /// Constructs an MBR encapsulating the coordinates <paramref name="xa"/>,<paramref name="ya"/> and <paramref name="xb"/>,<paramref name="yb"/>.
        /// </summary>
        /// <param name="xa">A x-coordinate.</param>
        /// <param name="ya">A y-coordinate.</param>
        /// <param name="xb">A x-coordinate.</param>
        /// <param name="yb">A y-coordinate.</param>
        public Mbr(double xa, double ya, double xb, double yb)
        {
            X = new Range(xa,xb);
            Y = new Range(ya,yb);
        }
        /// <summary>
        /// Constructs an MBR encapsulating the given <paramref name="x"/>-range and <paramref name="y"/>-range.
        /// </summary>
        /// <param name="x">A x-axis range.</param>
        /// <param name="y">A y-axis range.</param>
        public Mbr(Range x, Range y)
        {
            X = x;
            Y = y;
        }
        /// <summary>
        /// Constructs an MBR encapsulating the given <paramref name="x"/>-range and <paramref name="y"/>-range.
        /// </summary>
        /// <param name="x">A x-axis range.</param>
        /// <param name="y">A y-axis range.</param>
        public Mbr(IRange<double> x, IRange<double> y)
        {
            X = new Range(x);
            Y = new Range(y);
        }
        /// <summary>
        /// Constructs an MBR encapsulating the coordinate pair <paramref name="p"/>.
        /// </summary>
        /// <param name="p">A point.</param>
        public Mbr(Point2 p)
            : this(p.X,p.Y)
        { }
        /// <summary>
        /// Constructs an MBR encapsulating the coordinate pair <paramref name="p"/>.
        /// </summary>
        /// <param name="p">A point.</param>
        public Mbr(ICoordinatePair<double> p)
            : this(p.X,p.Y)
        { }
        /// <summary>
        /// Constructs an MBR encapsulating the coordinate pairs <paramref name="a"/> and <paramref name="b"/>.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        public Mbr(Point2 a, Point2 b)
            : this(a.X,a.Y,b.X,b.Y)
        { }
        /// <summary>
        /// Constructs an MBR encapsulating the coordinate pairs <paramref name="a"/> and <paramref name="b"/>.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        public Mbr(ICoordinatePair<double> a, ICoordinatePair<double> b)
            : this(a.X, a.Y, b.X, b.Y)
        { }
        /// <summary>
        /// Constructs a new MBR with the same bounds as the given MBR.
        /// </summary>
        /// <param name="mbr">The MBR to copy the bounds from.</param>
        public Mbr(IMbr<double> mbr)
            : this(mbr.XMin,mbr.YMin,mbr.XMax,mbr.YMax) { }

        /// <summary>
        /// The minimum encompassed value on the x-axis. 
        /// </summary>
        public double XMin
        {
            get { return X.Low; }
        }
        /// <summary>
        /// The maximum encompassed value on the x-axis. 
        /// </summary>
        public double XMax
        {
            get { return X.High; }
        }
        /// <summary>
        /// The minimum encompassed value on the y-axis. 
        /// </summary>
        public double YMin
        {
            get { return Y.Low; }
        }
        /// <summary>
        /// The maximum encompassed value on the y-axis. 
        /// </summary>
        public double YMax
        {
            get { return Y.High; }
        }
        /// <summary>
        /// The width of the MBR.
        /// </summary>
        public double Width
        {
            get { return X.GetMagnitude(); }
        }
        /// <summary>
        /// The height of the MBR.
        /// </summary>
        public double Height
        {
            get { return Y.GetMagnitude(); }
        }
        /// <summary>
        /// The minimum coordinate of this MBR.
        /// </summary>
        public Point2 Min
        {
            get { return new Point2(XMin,YMin);}
        }
        IPoint2<double> IMbr<double>.Min
        {
            get { return Min; }
        }
        /// <summary>
        /// The maximum coordinate of this MBR.
        /// </summary>
        public Point2 Max
        {
            get { return new Point2(XMax, YMax); }
        }
        IPoint2<double> IMbr<double>.Max
        {
            get { return Max; }
        }
        /// <summary>
        /// Calculates the area of this MBR.
        /// </summary>
        /// <returns>The area.</returns>
        public double GetArea()
        {
            return Width * Height;
        }

        /// <summary>
        /// Calculates the distance between this MBR and a point <paramref name="p"/>.
        /// </summary>
        /// <param name="p">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(Point2 p)
        {
            return System.Math.Sqrt(DistanceSquared(p));
        }
        /// <summary>
        /// Calculates the distance between this MBR and a point <paramref name="p"/>.
        /// </summary>
        /// <param name="p">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(IPoint2<double> p)
        {
            return System.Math.Sqrt(DistanceSquared(p));
        }
        /// <summary>
        /// Calculates the squared distance between this MBR and a point <paramref name="p"/>.
        /// </summary>
        /// <param name="p">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(Point2 p)
        {
            return X.DistanceSquared(p.X) + Y.DistanceSquared(p.Y);
        }
        /// <summary>
        /// Calculates the squared distance between this MBR and a point <paramref name="p"/>.
        /// </summary>
        /// <param name="p">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(IPoint2<double> p)
        {
            return X.DistanceSquared(p.X) + Y.DistanceSquared(p.Y);
        }
        /// <summary>
        /// Calculates the distance between this MBR and another <paramref name="mbr"/>.
        /// </summary>
        /// <param name="mbr">The MBR to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(IMbr<double> mbr)
        {
            return System.Math.Sqrt(DistanceSquared(mbr));
        }
        /// <summary>
        /// Calculates the distance between this MBR and another <paramref name="mbr"/>.
        /// </summary>
        /// <param name="mbr">The MBR to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(Mbr mbr)
        {
            return System.Math.Sqrt(DistanceSquared(mbr));
        }
        /// <summary>
        /// Calculates the squared distance between this MBR and another <paramref name="mbr"/>.
        /// </summary>
        /// <param name="mbr">The MBR to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(IMbr<double> mbr)
        {
            return X.DistanceSquared(new Range(mbr.XMin, mbr.XMax)) + X.DistanceSquared(new Range(mbr.YMin, mbr.YMax));
        }
        /// <summary>
        /// Calculates the squared distance between this MBR and another <paramref name="mbr"/>.
        /// </summary>
        /// <param name="mbr">The MBR to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(Mbr mbr)
        {
            return X.DistanceSquared(mbr.X) + X.DistanceSquared(mbr.Y);
        }
        /// <summary>
        /// Determines if a point intersects this MBR.
        /// </summary>
        /// <param name="p">A point to test intersection with.</param>
        /// <returns>True when a point intersects this MBR.</returns>
        public bool Intersects(Point2 p)
        {
            return X.Intersects(p.X) && Y.Intersects(p.Y);
        }
        /// <summary>
        /// Determines if a point intersects this MBR.
        /// </summary>
        /// <param name="p">A point to test intersection with.</param>
        /// <returns>True when a point intersects this MBR.</returns>
        public bool Intersects(IPoint2<double> p)
        {
            return X.Intersects(p.X) && Y.Intersects(p.Y);
        }
        /// <summary>
        /// Determines if this MBR intersects another <paramref name="mbr"/>.
        /// </summary>
        /// <param name="mbr">A MBR to test intersection with.</param>
        /// <returns>True when a MBR intersects this MBR.</returns>
        public bool Intersects(Mbr mbr)
        {
            return X.Intersects(mbr.X)
                && Y.Intersects(mbr.Y)
            ;
        }
        /// <summary>
        /// Determines if this MBR intersects another <paramref name="mbr"/>.
        /// </summary>
        /// <param name="mbr">A MBR to test intersection with.</param>
        /// <returns>True when a MBR intersects this MBR.</returns>
        public bool Intersects(IMbr<double> mbr)
        {
            return X.Intersects(new Range(mbr.XMin, mbr.XMax))
                && Y.Intersects(new Range(mbr.YMin, mbr.YMax))
            ;
        }

        IMbr<double> IHasMbr<double>.GetMbr()
        {
            return this;
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(Mbr other)
        {
            return X.Equals(other.X) && Y.Equals(other.Y);
        }
        /// <summary>
        /// Indicates whether the current object is equal to another object.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(IMbr<double> other)
        {
            return !ReferenceEquals(null,other)
                && X.Low == other.XMin
                && X.High == other.XMax
                && Y.Low == other.YMin
                && Y.High == other.YMax
            ;
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        /// <param name="obj">Another object to compare to.</param>
        /// <filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            return null != obj
                && (
                    (obj is Mbr) ? Equals((Mbr)obj)
                    :
                    (obj is IMbr<double> && Equals(obj as IMbr<double>))
                )
            ;
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return X.GetHashCode() ^ Y.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Concat("x:[",X, "],y:[", Y,']');
        }
        /// <summary>
        /// Calculates the centroid.
        /// </summary>
        /// <returns>A centroid.</returns>
        public Point2 GetCentroid()
        {
            return new Point2(X.Mid,Y.Mid);
        }
        IPoint2<double> IHasPlanarCentroid<double>.GetCentroid()
        {
            return GetCentroid();
        }

        public Mbr Union(double x, double y)
        {
            return new Mbr(X.Union(x),Y.Union(y));
        }
        IMbr<double> IMbr<double>.Union(double x, double y)
        {
            return Union(x, y);
        }
        public Mbr Union(Range x, Range y)
        {
            return new Mbr(X.Union(x),Y.Union(y));
        }
        public Mbr Union(IRange<double> x, IRange<double> y)
        {
            return Union(new Range(x), new Range(y));
        }
        IMbr<double> IMbr<double>.Union(IRange<double> x, IRange<double> y)
        {
            return Union(x, y);
        }
        public Mbr Union(Point2 p)
        {
            return Union(p.X, p.Y);
        }
        public Mbr Union(ICoordinatePair<double> p)
        {
            return Union(new Point2(p));
        }
        IMbr<double> IMbr<double>.Union(ICoordinatePair<double> p)
        {
            return Union(p);
        }
        public Mbr Union(Point2 a, Point2 b)
        {
            return Union(new Range(a.X, b.X), new Range(a.Y, b.Y));
        }
        public Mbr Union(ICoordinatePair<double> a, ICoordinatePair<double> b)
        {
            return Union(new Point2(a),new Point2(b));
        }
        IMbr<double> IMbr<double>.Union(ICoordinatePair<double> a, ICoordinatePair<double> b)
        {
            return Union(a, b);
        }
        public Mbr Union(Mbr mbr)
        {
            return Union(mbr.X, mbr.Y);
        }
        public Mbr Union(IMbr<double> mbr)
        {
            return Union(new Range(mbr.XMin, mbr.XMax), new Range(mbr.YMin, mbr.YMax));
        }    
        IMbr<double> IMbr<double>.Union(IMbr<double> mbr)
        {
            return Union(mbr);
        }
        /// <summary>
        /// Determines if the given MBR contains this MBR.
        /// </summary>
        /// <param name="mbr">The MBR to test.</param>
        /// <returns>True if this MBR is within the given MBR.</returns>
        public bool Within(Mbr mbr)
        {
            return mbr.X.Low <= X.Low
                && X.High <= mbr.X.High
                && mbr.Y.Low <= Y.Low
                && Y.High <= mbr.Y.High
            ;
        }
        /// <summary>
        /// Determines if the given MBR contains this MBR.
        /// </summary>
        /// <param name="mbr">The MBR to test.</param>
        /// <returns>True if this MBR is within the given MBR.</returns>
        public bool Within(IMbr<double> mbr)
        {
            return mbr.XMin <= X.Low
                && X.High <= mbr.XMax
                && mbr.YMin <= Y.Low
                && Y.High <= mbr.YMax
            ;
        }
        /// <summary>
        /// Gets a copy of the MBR at the same center point but a scaled width and height.
        /// </summary>
        /// <param name="scale">The scaling factor for the width and height.</param>
        /// <returns>A Mbr.</returns>
        public Mbr GetScaled(double scale)
        {
            return new Mbr(
                X.GetScaled(scale),
                Y.GetScaled(scale)
            );
        }
        IMbr<double> IMbr<double>.GetScaled(double scale)
        {
            return GetScaled(scale);
        }
        /// <summary>
        /// Gets a copy of the MBR at the same center point but with the given dimensions.
        /// </summary>
        /// <param name="size">The dimensions.</param>
        /// <returns>A MBR.</returns>
        public Mbr GetResized(Vector2 size)
        {
            return new Mbr(
                X.GetResized(size.X),
                Y.GetResized(size.Y)
            );
        }
        /// <summary>
        /// Gets a copy of the MBR at the same center point but with the given dimensions.
        /// </summary>
        /// <param name="size">The dimensions.</param>
        /// <returns>A MBR.</returns>
        public Mbr GetResized(IVector2<double> size)
        {
            return GetResized(new Vector2(size));
        }
        IMbr<double> IMbr<double>.GetResized(IVector2<double> size)
        {
            return GetResized(size);
        }
        /// <summary>
        /// Gets a copy of the MBR at the given <paramref name="center"/> point but with the same dimensions.
        /// </summary>
        /// <param name="center">The center point.</param>
        /// <returns>A MBR.</returns>
        public Mbr GetCentered(Point2 center)
        {
            return new Mbr(
                X.GetCentered(center.X),
                Y.GetCentered(center.Y)
            );
        }
        /// <summary>
        /// Gets a copy of the MBR at the given <paramref name="center"/> point but with the same dimensions.
        /// </summary>
        /// <param name="center">The center point.</param>
        /// <returns>A MBR.</returns>
        public Mbr GetCentered(ICoordinatePair<double> center)
        {
            return GetCentered(new Point2(center));
        }
        IMbr<double> IMbr<double>.GetCentered(ICoordinatePair<double> center)
        {
            return GetCentered(center);
        }

        /// <summary>
        /// Determines if this MBR and some <paramref name="other"/> share only boundaries.
        /// </summary>
        /// <param name="other">An MBR.</param>
        /// <returns>True when only boundaries occupy the same space.</returns>
        public bool Touches(Mbr other)
        {
            return (X.Touches(other.X) && Y.Intersects(other.Y))
                || (Y.Touches(other.Y) && X.Intersects(other.X))
            ;
        }
        /// <summary>
        /// Determines if this MBR and some <paramref name="other"/> share only boundaries.
        /// </summary>
        /// <param name="other">An MBR.</param>
        /// <returns>True when only boundaries occupy the same space.</returns>
        public bool Touches(IMbr<double> other)
        {
            return Touches(new Mbr(other));
        }

        /// <summary>
        /// Determines if this MBR and some <paramref name="other"/> share some space of the same dimension.
        /// </summary>
        /// <param name="other">A MBR.</param>
        /// <returns>True when the MBRs share space of the same dimension.</returns>
        public bool Overlaps(Mbr other)
        {
            return X.Overlaps(other.X) && Y.Overlaps(other.Y);
        }
        /// <summary>
        /// Determines if this MBR and some <paramref name="other"/> share some space of the same dimension.
        /// </summary>
        /// <param name="other">A MBR.</param>
        /// <returns>True when the MBRs share space of the same dimension.</returns>
        public bool Overlaps(IMbr<double> other)
        {
            return Overlaps(new Mbr(other));
        }

        /// <summary>
        /// Determines if this MBR and some <paramref name="other"/> share no space.
        /// </summary>
        /// <param name="other">A MBR.</param>
        /// <returns>True if the MBRs do not occupy any of the same space.</returns>
        public bool Disjoint(Mbr other)
        {
            return X.Disjoint(other.X) || Y.Disjoint(other.Y);
        }
        /// <summary>
        /// Determines if this MBR and some <paramref name="other"/> share no space.
        /// </summary>
        /// <param name="other">A MBR.</param>
        /// <returns>True if the MBRs do not occupy any of the same space.</returns>
        public bool Disjoint(IMbr<double> other)
        {
            return Disjoint(new Mbr(other));
        }

        /// <summary>
        /// Determines if some <paramref name="other"/> MBR is within the interior of this MBR.
        /// </summary>
        /// <param name="other">An Mbr.</param>
        /// <returns>True when this MBR contains another.</returns>
        public bool Contains(Mbr other)
        {
            return X.Contains(other.X) && Y.Contains(other.Y);
        }
        /// <summary>
        /// Determines if some <paramref name="other"/> MBR is within the interior of this MBR.
        /// </summary>
        /// <param name="other">An Mbr.</param>
        /// <returns>True when this MBR contains another.</returns>
        public bool Contains(IMbr<double> other)
        {
            return Contains(new Mbr(other));
        }

        /// <summary>
        /// Determines if this MBR crosses some <paramref name="other"/>.
        /// </summary>
        /// <param name="other">The MBR to test.</param>
        /// <returns>True if this MBR crosses the <paramref name="other"/>.</returns>
        public bool Crosses(Mbr other)
        {
            return (
                (0 == X.GetMagnitude() || 0 == Y.GetMagnitudeSquared())
                && X.Overlaps(other.Y)
                && Y.Overlaps(other.Y)
            );
        }
        /// <summary>
        /// Determines if this MBR crosses some <paramref name="other"/>.
        /// </summary>
        /// <param name="other">The MBR to test.</param>
        /// <returns>True if this MBR crosses the <paramref name="other"/>.</returns>
        public bool Crosses(IMbr<double> other)
        {
            return Crosses(new Mbr(other));
        }

    }
}
